package com.jia.shop.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jia.constant.RedisConstant;
import com.jia.shop.constant.ShopRedisConstant;
import com.jia.shop.entity.Shop;
import com.jia.shop.mapper.ShopMapper;
import com.jia.shop.service.ShopService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * (Shop)表服务实现类
 *
 * @author makejava
 * @since 2024-10-03 16:45:43
 */
@Service("shopService")
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements ShopService {

    public final StringRedisTemplate stringRedisTemplate;

    public final TransactionTemplate transactionTemplate;

    public ShopServiceImpl(StringRedisTemplate stringRedisTemplate,
                           TransactionTemplate transactionTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.transactionTemplate = transactionTemplate;
    }

    public static final String SHOP_REDIS_PREFIX = RedisConstant.PROJECT_REDIS_PREFIX +
            ShopRedisConstant.SHOP_PREFIX;

    private static final ExecutorService executorService = Executors.newFixedThreadPool(8);

    @Override
    public Shop queryById(Long id) {
        Shop shop = shopCacheRebuild(id);
        if (Objects.isNull(shop)) {
            throw new RuntimeException("Shop not found");
        }
        return shop;
    }

    private Shop shopCacheRebuild(Long id) {
        // 构建id key
        String idKey = SHOP_REDIS_PREFIX + RedisConstant.ID_PREFIX + id;
        // 构建 lock key
        String lockKey = SHOP_REDIS_PREFIX + RedisConstant.LOCK_PREFIX + RedisConstant.ID_PREFIX + id;

        // 从缓存中获取商铺
        String shopJson = stringRedisTemplate.opsForValue().get(idKey);
        if (StringUtils.isNotBlank(shopJson)) {
            if (RedisConstant.NULL_VALUE.equals(shopJson)) {
                return null;
            }
            // 存在就直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }

        // 加锁
        if (!lock(lockKey)) {
            // 加锁失败
            try {
                // 延迟递归重试
                Thread.sleep(500);
                return shopCacheRebuild(id);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        // 加锁成功
        // 不存在 从数据库中获取
        Shop shop = null;
        try {
            shop = baseMapper.selectById(id);
            if (Objects.isNull(shop)) {
                stringRedisTemplate.opsForValue().set(idKey, RedisConstant.NULL_VALUE, 2, TimeUnit.MINUTES);
                return null;
            }
            // 写入缓存
            stringRedisTemplate.opsForValue().set(idKey, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);
        } finally {
            unlock(lockKey);
        }


        return shop;

    }

    private boolean lock(String key) {
        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 30, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(b);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }


    @Override
    public Page<Shop> queryByPage(Shop shop, Page page) {
        QueryWrapper<Shop> wrapper = new QueryWrapper<>();
        wrapper.eq(!Objects.isNull(shop.getTypeId()), "type_id", shop.getTypeId());
        wrapper.likeRight(StringUtils.isNotEmpty(shop.getName()), "name", shop.getName());
        return this.baseMapper.selectPage(page, wrapper);
    }

    @Override
    public boolean deleteById(Long id) {
        this.baseMapper.deleteById(id);
        return true;
    }

    @Override
    public Shop update(Shop shop) {
        Long id = shop.getId();
        String idKey = SHOP_REDIS_PREFIX + RedisConstant.ID_PREFIX + id;
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    if (Objects.isNull(id)) {
                        throw new RuntimeException("id is null");
                    }
                    baseMapper.updateById(shop);
                    stringRedisTemplate.delete(idKey);
                } catch (Exception e) {
                    status.setRollbackOnly();
                }
            }
        });
        return shop;
    }

    @Override
    public Shop insert(Shop shop) {
        this.baseMapper.insert(shop);
        return shop;
    }


}

